package com.dabomstew.pkrandom.newnds;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import com.dabomstew.pkrandom.RomFunctions;
import com.dabomstew.pkrandom.gui.RandomizerGUI;

import cuecompressors.BLZCoder;

/*----------------------------------------------------------------------------*/
/*--  NDSRom.java - base class for opening/saving ROMs                      --*/
/*--  Code based on "Nintendo DS rom tool", copyright (C) DevkitPro         --*/
/*--  Original Code by Rafael Vuijk, Dave Murphy, Alexei Karpenko           --*/
/*--                                                                        --*/
/*--  Ported to Java by Dabomstew under the terms of the GPL:               --*/
/*--                                                                        --*/
/*--  This program is free software: you can redistribute it and/or modify  --*/
/*--  it under the terms of the GNU General Public License as published by  --*/
/*--  the Free Software Foundation, either version 3 of the License, or     --*/
/*--  (at your option) any later version.                                   --*/
/*--                                                                        --*/
/*--  This program is distributed in the hope that it will be useful,       --*/
/*--  but WITHOUT ANY WARRANTY; without even the implied warranty of        --*/
/*--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the          --*/
/*--  GNU General Public License for more details.                          --*/
/*--                                                                        --*/
/*--  You should have received a copy of the GNU General Public License     --*/
/*--  along with this program. If not, see <http://www.gnu.org/licenses/>.  --*/
/*----------------------------------------------------------------------------*/

public class NDSRom {

	private String romFilename;
	private RandomAccessFile baseRom;
	private boolean romOpen;
	private Map<String, NDSFile> files;
	private Map<Integer, NDSFile> filesByID;
	private Map<Integer, NDSY9Entry> arm9overlaysByFileID;
	private NDSY9Entry[] arm9overlays;
	private byte[] fat;
	private String tmpFolder;
	private boolean writingEnabled;
	private boolean arm9_open, arm9_changed, arm9_has_footer;
	private boolean arm9_compressed;
	private int arm9_szmode, arm9_szoffset;
	private byte[] arm9_footer;
	private byte[] arm9_ramstored;

	private static final int arm9_align = 0x1FF, arm7_align = 0x1FF;
	private static final int fnt_align = 0x1FF, fat_align = 0x1FF;
	private static final int banner_align = 0x1FF, file_align = 0x1FF;

	public NDSRom(String filename) throws IOException {
		this.romFilename = filename;
		this.baseRom = new RandomAccessFile(filename, "r");
		this.romOpen = true;
		// TMP folder?
		String rawFilename = new File(filename).getName();
		String dataFolder = "tmp_"
				+ rawFilename.substring(0, rawFilename.lastIndexOf('.'));
		// remove nonsensical chars
		dataFolder = dataFolder.replaceAll("[^A-Za-z0-9_]+", "");
		File tmpFolder = new File(RandomizerGUI.getRootPath() + dataFolder);
		tmpFolder.mkdir();
		if (tmpFolder.canWrite()) {
			writingEnabled = true;
			this.tmpFolder = RandomizerGUI.getRootPath() + dataFolder
					+ File.separator;
			tmpFolder.deleteOnExit();
		} else {
			writingEnabled = false;
		}
		readFileSystem();
		arm9_open = false;
		arm9_changed = false;
		arm9_ramstored = null;
	}

	public void reopenROM() throws IOException {
		if (!this.romOpen) {
			this.baseRom = new RandomAccessFile(this.romFilename, "r");
			this.romOpen = true;
		}
	}

	public void closeROM() throws IOException {
		if (this.romOpen && this.baseRom != null) {
			this.baseRom.close();
			this.baseRom = null;
			this.romOpen = false;
		}
	}

	private void readFileSystem() throws IOException {
		baseRom.seek(0x40);
		int fntOffset = readFromFile(baseRom, 4);
		readFromFile(baseRom, 4); // fntSize not needed
		int fatOffset = readFromFile(baseRom, 4);
		int fatSize = readFromFile(baseRom, 4);

		// Read full FAT table
		baseRom.seek(fatOffset);
		fat = new byte[fatSize];
		baseRom.readFully(fat);

		Map<Integer, String> directoryPaths = new HashMap<Integer, String>();
		directoryPaths.put(0xF000, "");
		int dircount = readFromFile(baseRom, fntOffset + 0x6, 2);
		files = new HashMap<String, NDSFile>();
		filesByID = new HashMap<Integer, NDSFile>();

		// read fnt table
		baseRom.seek(fntOffset);
		int[] subTableOffsets = new int[dircount];
		int[] firstFileIDs = new int[dircount];
		int[] parentDirIDs = new int[dircount];
		for (int i = 0; i < dircount && i < 0x1000; i++) {
			subTableOffsets[i] = readFromFile(baseRom, 4) + fntOffset;
			firstFileIDs[i] = readFromFile(baseRom, 2);
			parentDirIDs[i] = readFromFile(baseRom, 2);
		}

		// get dirnames
		String[] directoryNames = new String[dircount];
		Map<Integer, String> filenames = new TreeMap<Integer, String>();
		Map<Integer, Integer> fileDirectories = new HashMap<Integer, Integer>();
		for (int i = 0; i < dircount && i < 0x1000; i++) {
			firstPassDirectory(i, subTableOffsets[i], firstFileIDs[i],
					directoryNames, filenames, fileDirectories);
		}

		// get full dirnames
		for (int i = 1; i < dircount && i < 0x1000; i++) {
			String dirname = directoryNames[i];
			if (dirname != null) {
				String fullDirName = "";
				int curDir = i;
				while (dirname != null && !dirname.isEmpty()) {
					if (!fullDirName.isEmpty()) {
						fullDirName = "/" + fullDirName;
					}
					fullDirName = dirname + fullDirName;
					int parentDir = parentDirIDs[curDir];
					if (parentDir >= 0xF001 && parentDir <= 0xFFFF) {
						curDir = parentDir - 0xF000;
						dirname = directoryNames[curDir];
					} else {
						break;
					}
				}
				directoryPaths.put(i + 0xF000, fullDirName);
			} else {
				directoryPaths.put(i + 0xF000, "");
			}
		}

		// parse files
		for (int fileID : filenames.keySet()) {
			String filename = filenames.get(fileID);
			int directory = fileDirectories.get(fileID);
			String dirPath = directoryPaths.get(directory + 0xF000);
			String fullFilename = filename;
			if (!dirPath.isEmpty()) {
				fullFilename = dirPath + "/" + filename;
			}
			NDSFile nf = new NDSFile(this);
			int start = readFromByteArr(fat, fileID * 8, 4);
			int end = readFromByteArr(fat, fileID * 8 + 4, 4);
			nf.offset = start;
			nf.size = end - start;
			nf.fullPath = fullFilename;
			nf.fileID = fileID;
			files.put(fullFilename, nf);
			filesByID.put(fileID, nf);
		}

		// arm9 overlays
		int arm9_ovl_table_offset = readFromFile(baseRom, 0x50, 4);
		int arm9_ovl_table_size = readFromFile(baseRom, 0x54, 4);
		int arm9_ovl_count = arm9_ovl_table_size / 32;
		byte[] y9table = new byte[arm9_ovl_table_size];
		arm9overlays = new NDSY9Entry[arm9_ovl_count];
		arm9overlaysByFileID = new HashMap<Integer, NDSY9Entry>();
		baseRom.seek(arm9_ovl_table_offset);
		baseRom.readFully(y9table);

		// parse overlays
		for (int i = 0; i < arm9_ovl_count; i++) {
			NDSY9Entry overlay = new NDSY9Entry(this);
			int fileID = readFromByteArr(y9table, i * 32 + 24, 4);
			int start = readFromByteArr(fat, fileID * 8, 4);
			int end = readFromByteArr(fat, fileID * 8 + 4, 4);
			overlay.offset = start;
			overlay.size = end - start;
			overlay.original_size = end - start;
			overlay.fileID = fileID;
			overlay.overlay_id = i;
			overlay.ram_address = readFromByteArr(y9table, i * 32 + 4, 4);
			overlay.ram_size = readFromByteArr(y9table, i * 32 + 8, 4);
			overlay.bss_size = readFromByteArr(y9table, i * 32 + 12, 4);
			overlay.static_start = readFromByteArr(y9table, i * 32 + 16, 4);
			overlay.static_end = readFromByteArr(y9table, i * 32 + 20, 4);
			overlay.compressed_size = readFromByteArr(y9table, i * 32 + 28, 3);
			overlay.compress_flag = y9table[i * 32 + 31] & 0xFF;
			arm9overlays[i] = overlay;
			arm9overlaysByFileID.put(fileID, overlay);
		}
	}

	public void saveTo(String filename) throws IOException {
		this.reopenROM();

		// Initialise new ROM
		RandomAccessFile fNew = new RandomAccessFile(filename, "rw");

		int headersize = readFromFile(this.baseRom, 0x84, 4);
		this.baseRom.seek(0);
		copy(this.baseRom, fNew, headersize);

		// arm9
		int arm9_offset = ((int) (fNew.getFilePointer() + arm9_align))
				& (~arm9_align);
		int old_arm9_offset = readFromFile(this.baseRom, 0x20, 4);
		int arm9_size = readFromFile(this.baseRom, 0x2C, 4);
		if (arm9_open && arm9_changed) {
			// custom arm9
			byte[] newARM9 = getARM9();
			if (arm9_compressed) {
				newARM9 = new BLZCoder(null).BLZ_EncodePub(newARM9, true,
						false, "arm9.bin");
				if (arm9_szoffset > 0) {
					int newValue = arm9_szmode == 1 ? newARM9.length
							: newARM9.length + 0x4000;
					writeToByteArr(newARM9, arm9_szoffset, 3, newValue);
				}
			}
			arm9_size = newARM9.length;
			// copy new arm9
			fNew.seek(arm9_offset);
			fNew.write(newARM9);
			// footer?
			if (arm9_has_footer) {
				fNew.write(arm9_footer);
			}

		} else {
			// copy arm9+footer
			this.baseRom.seek(old_arm9_offset);
			fNew.seek(arm9_offset);
			copy(this.baseRom, fNew, arm9_size + 12);
		}

		// arm9 ovl
		int arm9_ovl_offset = (int) fNew.getFilePointer();
		int arm9_ovl_size = arm9overlays.length * 32;

		// don't actually write arm9 ovl yet

		// arm7
		int arm7_offset = ((int) (arm9_ovl_offset + arm9_ovl_size + arm7_align))
				& (~arm7_align);
		int old_arm7_offset = readFromFile(this.baseRom, 0x30, 4);
		int arm7_size = readFromFile(this.baseRom, 0x3C, 4);
		// copy arm7
		this.baseRom.seek(old_arm7_offset);
		fNew.seek(arm7_offset);
		copy(this.baseRom, fNew, arm7_size);

		// arm7 ovl
		int arm7_ovl_offset = (int) fNew.getFilePointer();
		int old_arm7_ovl_offset = readFromFile(this.baseRom, 0x58, 4);
		int arm7_ovl_size = readFromFile(this.baseRom, 0x5C, 4);

		// copy arm7 ovl
		this.baseRom.seek(old_arm7_ovl_offset);
		fNew.seek(arm7_ovl_offset);
		copy(this.baseRom, fNew, arm7_ovl_size);

		// banner
		int banner_offset = ((int) (fNew.getFilePointer() + banner_align))
				& (~banner_align);
		int old_banner_offset = readFromFile(this.baseRom, 0x68, 4);
		int banner_size = 0x840;
		// copy banner
		this.baseRom.seek(old_banner_offset);
		fNew.seek(banner_offset);
		copy(this.baseRom, fNew, banner_size);

		// filename table (doesn't change)
		int fnt_offset = ((int) (fNew.getFilePointer() + fnt_align))
				& (~fnt_align);
		int old_fnt_offset = readFromFile(this.baseRom, 0x40, 4);
		int fnt_size = readFromFile(this.baseRom, 0x44, 4);
		// copy fnt
		this.baseRom.seek(old_fnt_offset);
		fNew.seek(fnt_offset);
		copy(this.baseRom, fNew, fnt_size);

		// make space for the FAT table
		int fat_offset = ((int) (fNew.getFilePointer() + fat_align))
				& (~fat_align);
		int fat_size = fat.length;

		// Now for actual files
		// Make a new FAT as needed
		// also make a new y9 table
		byte[] newfat = new byte[fat.length];
		byte[] y9table = new byte[arm9overlays.length * 32];
		int base_offset = fat_offset + fat_size;
		int filecount = fat.length / 8;
		for (int fid = 0; fid < filecount; fid++) {
			int offset_of_file = (base_offset + file_align) & (~file_align);
			int file_len = 0;
			boolean copiedCustom = false;
			if (filesByID.containsKey(fid)) {
				byte[] customContents = filesByID.get(fid)
						.getOverrideContents();
				if (customContents != null) {
					// copy custom
					fNew.seek(offset_of_file);
					fNew.write(customContents);
					copiedCustom = true;
					file_len = customContents.length;
				}
			}
			if (arm9overlaysByFileID.containsKey(fid)) {
				NDSY9Entry entry = arm9overlaysByFileID.get(fid);
				int overlay_id = entry.overlay_id;
				byte[] customContents = entry.getOverrideContents();
				if (customContents != null) {
					// copy custom
					fNew.seek(offset_of_file);
					fNew.write(customContents);
					copiedCustom = true;
					file_len = customContents.length;
				}
				// regardless, fill in y9 table
				writeToByteArr(y9table, overlay_id * 32, 4, overlay_id);
				writeToByteArr(y9table, overlay_id * 32 + 4, 4,
						entry.ram_address);
				writeToByteArr(y9table, overlay_id * 32 + 8, 4, entry.ram_size);
				writeToByteArr(y9table, overlay_id * 32 + 12, 4, entry.bss_size);
				writeToByteArr(y9table, overlay_id * 32 + 16, 4,
						entry.static_start);
				writeToByteArr(y9table, overlay_id * 32 + 20, 4,
						entry.static_end);
				writeToByteArr(y9table, overlay_id * 32 + 24, 4, fid);
				writeToByteArr(y9table, overlay_id * 32 + 28, 3,
						entry.compressed_size);
				writeToByteArr(y9table, overlay_id * 32 + 31, 1,
						entry.compress_flag);
			}
			if (!copiedCustom) {
				// copy from original ROM
				int file_starts = readFromByteArr(fat, fid * 8, 4);
				int file_ends = readFromByteArr(fat, fid * 8 + 4, 4);
				file_len = file_ends - file_starts;
				this.baseRom.seek(file_starts);
				fNew.seek(offset_of_file);
				copy(this.baseRom, fNew, file_len);
			}
			// write to new FAT
			writeToByteArr(newfat, fid * 8, 4, offset_of_file);
			writeToByteArr(newfat, fid * 8 + 4, 4, offset_of_file + file_len);
			// update base_offset
			base_offset = offset_of_file + file_len;
		}

		// write new FAT table
		fNew.seek(fat_offset);
		fNew.write(newfat);

		// write y9 table
		fNew.seek(arm9_ovl_offset);
		fNew.write(y9table);

		// tidy up ending
		// base_offset is the end of the last file
		int newfilesize = base_offset;
		newfilesize = (newfilesize + 3) & ~3;
		int application_end_offset = newfilesize;
		if (newfilesize != base_offset) {
			fNew.seek(newfilesize - 1);
			fNew.write(0);
		}

		// calculate device capacity;
		newfilesize |= newfilesize >> 16;
		newfilesize |= newfilesize >> 8;
		newfilesize |= newfilesize >> 4;
		newfilesize |= newfilesize >> 2;
		newfilesize |= newfilesize >> 1;
		newfilesize++;
		if (newfilesize <= 128 * 1024) {
			newfilesize = 128 * 1024;
		}
		int devcap = -18;
		int x = newfilesize;
		while (x != 0) {
			x >>= 1;
			devcap++;
		}
		int devicecap = ((devcap < 0) ? 0 : devcap);

		// Update offsets in ROM header
		writeToFile(fNew, 0x20, 4, arm9_offset);
		writeToFile(fNew, 0x2C, 4, arm9_size);
		writeToFile(fNew, 0x30, 4, arm7_offset);
		writeToFile(fNew, 0x3C, 4, arm7_size);
		writeToFile(fNew, 0x40, 4, fnt_offset);
		writeToFile(fNew, 0x48, 4, fat_offset);
		writeToFile(fNew, 0x50, 4, arm9_ovl_offset);
		writeToFile(fNew, 0x58, 4, arm7_ovl_offset);
		writeToFile(fNew, 0x68, 4, banner_offset);
		writeToFile(fNew, 0x80, 4, application_end_offset);
		writeToFile(fNew, 0x14, 1, devicecap);

		// Update header CRC
		fNew.seek(0);
		byte[] headerForCRC = new byte[0x15E];
		fNew.readFully(headerForCRC);
		short crc = CRC16.calculate(headerForCRC, 0, 0x15E);
		writeToFile(fNew, 0x15E, 2, (crc & 0xFFFF));

		// done
		fNew.close();
		closeROM();
	}

	private void copy(RandomAccessFile from, RandomAccessFile to, int bytes)
			throws IOException {
		int sizeof_copybuf = Math.min(256 * 1024, bytes);
		byte[] copybuf = new byte[sizeof_copybuf];
		while (bytes > 0) {
			int size2 = (bytes >= sizeof_copybuf) ? sizeof_copybuf : bytes;
			int read = from.read(copybuf, 0, size2);
			to.write(copybuf, 0, read);
			bytes -= read;
		}
		copybuf = null;
	}

	// returns null if file doesn't exist
	public byte[] getFile(String filename) throws IOException {
		if (files.containsKey(filename)) {
			return files.get(filename).getContents();
		} else {
			return null;
		}
	}

	public byte[] getOverlay(int number) throws IOException {
		if (number >= 0 && number <= arm9overlays.length) {
			return arm9overlays[number].getContents();
		} else {
			return null;
		}
	}

	public byte[] getARM9() throws IOException {
		if (!arm9_open) {
			arm9_open = true;
			this.reopenROM();
			int arm9_offset = readFromFile(this.baseRom, 0x20, 4);
			int arm9_size = readFromFile(this.baseRom, 0x2C, 4);
			byte[] arm9 = new byte[arm9_size];
			this.baseRom.seek(arm9_offset);
			this.baseRom.readFully(arm9);
			// footer check
			int nitrocode = readFromFile(this.baseRom, 4);
			if (nitrocode == 0xDEC00621) {
				// found a footer
				arm9_footer = new byte[12];
				writeToByteArr(arm9_footer, 0, 4, 0xDEC00621);
				this.baseRom.read(arm9_footer, 4, 8);
				arm9_has_footer = true;
			} else {
				arm9_has_footer = false;
			}
			// Any extras?
			while ((readFromByteArr(arm9, arm9.length - 12, 4) == 0xDEC00621)
					|| ((readFromByteArr(arm9, arm9.length - 12, 4) == 0
							&& readFromByteArr(arm9, arm9.length - 8, 4) == 0 && readFromByteArr(
							arm9, arm9.length - 4, 4) == 0))) {
				if (!arm9_has_footer) {
					arm9_has_footer = true;
					arm9_footer = new byte[0];
				}
				byte[] newfooter = new byte[arm9_footer.length + 12];
				System.arraycopy(arm9, arm9.length - 12, newfooter, 0, 12);
				System.arraycopy(arm9_footer, 0, newfooter, 12,
						arm9_footer.length);
				arm9_footer = newfooter;
				byte[] newarm9 = new byte[arm9.length - 12];
				System.arraycopy(arm9, 0, newarm9, 0, arm9.length - 12);
				arm9 = newarm9;
			}
			// Compression?
			arm9_compressed = false;
			arm9_szoffset = 0;
			if (((int) arm9[arm9.length - 5]) >= 0x08
					&& ((int) arm9[arm9.length - 5]) <= 0x0B) {
				int compSize = readFromByteArr(arm9, arm9.length - 8, 3);
				if (compSize > (arm9.length * 9 / 10)
						&& compSize < (arm9.length * 11 / 10)) {
					arm9_compressed = true;
					byte[] compLength = new byte[3];
					writeToByteArr(compLength, 0, 3, arm9.length);
					List<Integer> foundOffsets = RomFunctions.search(arm9,
							compLength);
					if (foundOffsets.size() == 1) {
						arm9_szmode = 1;
						arm9_szoffset = foundOffsets.get(0);
					} else {
						byte[] compLength2 = new byte[3];
						writeToByteArr(compLength2, 0, 3, arm9.length + 0x4000);
						List<Integer> foundOffsets2 = RomFunctions.search(arm9,
								compLength2);
						if (foundOffsets2.size() == 1) {
							arm9_szmode = 2;
							arm9_szoffset = foundOffsets2.get(0);
						} else {
						}
					}
				}
			}

			if (arm9_compressed) {
				arm9 = new BLZCoder(null).BLZ_DecodePub(arm9, "arm9.bin");
			}

			// Now actually make the copy or w/e
			if (writingEnabled) {
				File arm9file = new File(tmpFolder + "arm9.bin");
				FileOutputStream fos = new FileOutputStream(arm9file);
				fos.write(arm9);
				fos.close();
				arm9file.deleteOnExit();
				this.arm9_ramstored = null;
				return arm9;
			} else {
				this.arm9_ramstored = arm9;
				byte[] newcopy = new byte[arm9.length];
				System.arraycopy(arm9, 0, newcopy, 0, arm9.length);
				return newcopy;
			}
		} else {
			if (writingEnabled) {
				FileInputStream fis = new FileInputStream(tmpFolder
						+ "arm9.bin");
				byte[] file = new byte[fis.available()];
				fis.read(file);
				fis.close();
				return file;
			} else {
				byte[] newcopy = new byte[this.arm9_ramstored.length];
				System.arraycopy(this.arm9_ramstored, 0, newcopy, 0,
						this.arm9_ramstored.length);
				return newcopy;
			}
		}
	}

	// returns null if file doesn't exist
	public void writeFile(String filename, byte[] data) throws IOException {
		if (files.containsKey(filename)) {
			files.get(filename).writeOverride(data);
		}
	}

	public void writeOverlay(int number, byte[] data) throws IOException {
		if (number >= 0 && number <= arm9overlays.length) {
			arm9overlays[number].writeOverride(data);
		}
	}

	public void writeARM9(byte[] arm9) throws IOException {
		if (!arm9_open) {
			getARM9();
		}
		arm9_changed = true;
		if (writingEnabled) {
			FileOutputStream fos = new FileOutputStream(new File(tmpFolder
					+ "arm9.bin"));
			fos.write(arm9);
			fos.close();
		} else {
			if (this.arm9_ramstored.length == arm9.length) {
				// copy new in
				System.arraycopy(arm9, 0, this.arm9_ramstored, 0, arm9.length);
			} else {
				// make new array
				this.arm9_ramstored = null;
				this.arm9_ramstored = new byte[arm9.length];
				System.arraycopy(arm9, 0, this.arm9_ramstored, 0, arm9.length);
			}
		}
	}

	private void firstPassDirectory(int dir, int subTableOffset,
			int firstFileID, String[] directoryNames,
			Map<Integer, String> filenames,
			Map<Integer, Integer> fileDirectories) throws IOException {
		// read subtable
		baseRom.seek(subTableOffset);
		while (true) {
			int control = baseRom.read();
			if (control == 0x00) {
				// done
				break;
			}
			int namelen = control & 0x7F;
			byte[] rawname = new byte[namelen];
			baseRom.readFully(rawname);
			String name = new String(rawname, "US-ASCII");
			if ((control & 0x80) > 0x00) {
				// sub-directory
				int subDirectoryID = readFromFile(baseRom, 2);
				directoryNames[subDirectoryID - 0xF000] = name;
			} else {
				int fileID = firstFileID++;
				filenames.put(fileID, name);
				fileDirectories.put(fileID, dir);
			}
		}
	}

	// Helper methods to get variable-size ints out of files

	public String getTmpFolder() {
		return tmpFolder;
	}

	public RandomAccessFile getBaseRom() {
		return baseRom;
	}

	public boolean isWritingEnabled() {
		return writingEnabled;
	}

	public int readFromByteArr(byte[] data, int offset, int size) {
		int result = 0;
		for (int i = 0; i < size; i++) {
			result |= (data[i + offset] & 0xFF) << (i * 8);
		}
		return result;
	}

	public void writeToByteArr(byte[] data, int offset, int size, int value) {
		for (int i = 0; i < size; i++) {
			data[offset + i] = (byte) ((value >> (i * 8)) & 0xFF);
		}
	}

	public int readFromFile(RandomAccessFile file, int size) throws IOException {
		return readFromFile(file, -1, size);
	}

	// use -1 offset to read from current position
	// useful if you want to read blocks
	public int readFromFile(RandomAccessFile file, int offset, int size)
			throws IOException {
		byte[] buf = new byte[size];
		if (offset >= 0)
			file.seek(offset);
		file.readFully(buf);
		int result = 0;
		for (int i = 0; i < size; i++) {
			result |= (buf[i] & 0xFF) << (i * 8);
		}
		return result;
	}

	public void writeToFile(RandomAccessFile file, int size, int value)
			throws IOException {
		writeToFile(file, -1, size, value);
	}

	public void writeToFile(RandomAccessFile file, int offset, int size,
			int value) throws IOException {
		byte[] buf = new byte[size];
		for (int i = 0; i < size; i++) {
			buf[i] = (byte) ((value >> (i * 8)) & 0xFF);
		}
		if (offset >= 0)
			file.seek(offset);
		file.write(buf);
	}

}
